{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "c8d86cb5",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "正在读取 C:\\Users\\jiang\\Desktop\\python结课\\ASD ...\n",
      "成功读取 124 个样本\n",
      "正在读取 C:\\Users\\jiang\\Desktop\\python结课\\TD ...\n",
      "成功读取 145 个样本\n",
      "\n",
      "数据集合并完成，共有 269 个样本\n",
      "\n",
      "特征选择完成，保留了 43 个特征:\n",
      "['Gaze_X_mean', 'Gaze_X_std', 'Gaze_X_min', 'Gaze_X_max', 'Gaze_X_range', 'Gaze_X_median', 'Gaze_X_25%', 'Gaze_X_75%', 'Gaze_X_skew', 'Gaze_X_kurt', 'Gaze_X_slope', 'Gaze_X_std_change', 'Gaze_Y_mean', 'Gaze_Y_std', 'Gaze_Y_min', 'Gaze_Y_max', 'Gaze_Y_range', 'Gaze_Y_median', 'Gaze_Y_25%', 'Gaze_Y_75%', 'Gaze_Y_skew', 'Gaze_Y_kurt', 'Gaze_Y_slope', 'Gaze_Y_std_change', 'Expression_mean', 'Expression_std', 'Expression_min', 'Expression_max', 'Expression_range', 'Expression_median', 'Expression_25%', 'Expression_75%', 'Expression_skew', 'Expression_kurt', 'Expression_slope', 'Expression_std_change', 'gaze_dist_mean', 'gaze_dist_std', 'gaze_dist_max', 'gaze_speed_x_mean', 'gaze_speed_y_mean', 'expr_change_mean', 'expr_change_std']\n",
      "\n",
      "数据集统计：\n",
      "总样本数: 269\n",
      "训练集样本数: 215 (80%)\n",
      "测试集样本数: 54 (20%)\n",
      "训练集标签分布: label\n",
      "0    116\n",
      "1     99\n",
      "Name: count, dtype: int64\n",
      "测试集标签分布: label\n",
      "0    29\n",
      "1    25\n",
      "Name: count, dtype: int64\n",
      "\n",
      "=== SVM 模型训练与评估 ===\n",
      "训练时间: 0.03 秒\n",
      "准确率: 0.8333\n",
      "分类报告:\n",
      "              precision    recall  f1-score   support\n",
      "\n",
      "          TD       0.86      0.83      0.84        29\n",
      "         ASD       0.81      0.84      0.82        25\n",
      "\n",
      "    accuracy                           0.83        54\n",
      "   macro avg       0.83      0.83      0.83        54\n",
      "weighted avg       0.83      0.83      0.83        54\n",
      "\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "d:\\python\\Lib\\site-packages\\sklearn\\feature_selection\\_univariate_selection.py:110: UserWarning: Features [27] are constant.\n",
      "  warnings.warn(\"Features %s are constant.\" % constant_features_idx, UserWarning)\n",
      "d:\\python\\Lib\\site-packages\\sklearn\\feature_selection\\_univariate_selection.py:111: RuntimeWarning: invalid value encountered in divide\n",
      "  f = msb / msw\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "=== 随机森林 模型训练与评估 ===\n",
      "训练时间: 0.42 秒\n",
      "准确率: 0.7407\n",
      "分类报告:\n",
      "              precision    recall  f1-score   support\n",
      "\n",
      "          TD       0.80      0.69      0.74        29\n",
      "         ASD       0.69      0.80      0.74        25\n",
      "\n",
      "    accuracy                           0.74        54\n",
      "   macro avg       0.74      0.74      0.74        54\n",
      "weighted avg       0.75      0.74      0.74        54\n",
      "\n",
      "\n",
      "=== 梯度提升树 模型训练与评估 ===\n",
      "训练时间: 1.07 秒\n",
      "准确率: 0.7593\n",
      "分类报告:\n",
      "              precision    recall  f1-score   support\n",
      "\n",
      "          TD       0.79      0.76      0.77        29\n",
      "         ASD       0.73      0.76      0.75        25\n",
      "\n",
      "    accuracy                           0.76        54\n",
      "   macro avg       0.76      0.76      0.76        54\n",
      "weighted avg       0.76      0.76      0.76        54\n",
      "\n",
      "\n",
      "特征重要性（随机森林 - 前20）:\n",
      "Gaze_Y_std: 0.0861\n",
      "Gaze_Y_std_change: 0.0753\n",
      "Gaze_Y_kurt: 0.0454\n",
      "gaze_dist_std: 0.0445\n",
      "Gaze_Y_range: 0.0412\n",
      "Gaze_Y_skew: 0.0375\n",
      "Gaze_X_std_change: 0.0373\n",
      "Gaze_Y_min: 0.0319\n",
      "Gaze_Y_75%: 0.0313\n",
      "gaze_speed_y_mean: 0.0305\n",
      "Gaze_X_max: 0.0303\n",
      "Gaze_X_std: 0.0279\n",
      "Gaze_X_mean: 0.0272\n",
      "Expression_slope: 0.0250\n",
      "Gaze_Y_mean: 0.0237\n",
      "Gaze_X_range: 0.0228\n",
      "Gaze_Y_median: 0.0224\n",
      "Gaze_X_kurt: 0.0215\n",
      "Gaze_Y_25%: 0.0210\n",
      "Gaze_X_min: 0.0209\n",
      "\n",
      "=== 模型比较 ===\n",
      "SVM: 准确率 = 0.8333, AUC = 0.8510\n",
      "随机森林: 准确率 = 0.7407, AUC = 0.8048\n",
      "梯度提升树: 准确率 = 0.7593, AUC = 0.8497\n"
     ]
    }
   ],
   "source": [
    "import os\n",
    "import numpy as np\n",
    "import pandas as pd\n",
    "from sklearn.model_selection import train_test_split, StratifiedKFold, GridSearchCV\n",
    "from sklearn.preprocessing import StandardScaler\n",
    "from sklearn.svm import SVC\n",
    "from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier\n",
    "from sklearn.metrics import accuracy_score, classification_report, confusion_matrix, roc_curve, auc\n",
    "import matplotlib.pyplot as plt\n",
    "import seaborn as sns\n",
    "from sklearn.pipeline import make_pipeline\n",
    "from sklearn.feature_selection import SelectKBest, f_classif\n",
    "import time  # 统一导入模块到顶部\n",
    "\n",
    "# 设置中文显示\n",
    "plt.rcParams[\"font.family\"] = [\"SimHei\"]\n",
    "plt.rcParams[\"axes.unicode_minus\"] = False  # 解决负号显示问题\n",
    "\n",
    "# 处理单个受试者的CSV文件，提取特征\n",
    "def process_subject_file(file_path, max_frames=2000):\n",
    "    \"\"\"\n",
    "    处理单个受试者的CSV文件，提取特征\n",
    "    :param file_path: CSV文件路径\n",
    "    :param max_frames: 最大帧数，用于统一样本长度\n",
    "    :return: 特征向量\n",
    "    \"\"\"\n",
    "    try:\n",
    "        df = pd.read_csv(file_path)\n",
    "        \n",
    "        # 确保数据包含必要的列\n",
    "        required_columns = ['Frame', 'Gaze_X', 'Gaze_Y', 'Expression']\n",
    "        if not all(col in df.columns for col in required_columns):\n",
    "            missing = [col for col in required_columns if col not in df.columns]\n",
    "            raise ValueError(f\"缺少列: {missing}\")\n",
    "        \n",
    "        # 截取前max_frames帧，不足则填充NaN\n",
    "        if len(df) > max_frames:\n",
    "            df = df.iloc[:max_frames]\n",
    "        else:\n",
    "            pad_len = max_frames - len(df)\n",
    "            pad_df = pd.DataFrame(np.nan, index=range(pad_len), columns=df.columns)\n",
    "            df = pd.concat([df, pad_df])\n",
    "        \n",
    "        # 提取特征并处理缺失值（先用ffill/bfill填充，再用均值填充残留NaN）\n",
    "        features = df[['Gaze_X', 'Gaze_Y', 'Expression']]\n",
    "        features = features.ffill().bfill()  # 替换fillna(method)\n",
    "        features = features.fillna(features.mean())  # 处理可能残留的NaN（如首尾全空）\n",
    "        \n",
    "        # 计算统计特征\n",
    "        stats = {}\n",
    "        for col in features.columns:\n",
    "            # 基本统计量\n",
    "            stats[f'{col}_mean'] = features[col].mean()\n",
    "            stats[f'{col}_std'] = features[col].std()\n",
    "            stats[f'{col}_min'] = features[col].min()\n",
    "            stats[f'{col}_max'] = features[col].max()\n",
    "            stats[f'{col}_range'] = stats[f'{col}_max'] - stats[f'{col}_min']\n",
    "            stats[f'{col}_median'] = features[col].median()\n",
    "            stats[f'{col}_25%'] = features[col].quantile(0.25)\n",
    "            stats[f'{col}_75%'] = features[col].quantile(0.75)\n",
    "            stats[f'{col}_skew'] = features[col].skew()  # 偏度\n",
    "            stats[f'{col}_kurt'] = features[col].kurt()  # 峰度\n",
    "            \n",
    "            # 趋势特征（斜率）\n",
    "            x = np.arange(len(features[col]))\n",
    "            slope, _ = np.polyfit(x, features[col], 1)\n",
    "            stats[f'{col}_slope'] = slope\n",
    "            \n",
    "            # 方差变化（滚动窗口标准差的均值）\n",
    "            rolling_std = features[col].rolling(window=100, min_periods=1).std()\n",
    "            stats[f'{col}_std_change'] = rolling_std.mean()\n",
    "        \n",
    "        # 计算Gaze_X和Gaze_Y的联合特征\n",
    "        gaze_dist = np.sqrt(features['Gaze_X']**2 + features['Gaze_Y']**2)\n",
    "        stats['gaze_dist_mean'] = gaze_dist.mean()\n",
    "        stats['gaze_dist_std'] = gaze_dist.std()\n",
    "        stats['gaze_dist_max'] = gaze_dist.max()\n",
    "        \n",
    "        # 视线移动速度（差分的绝对值）\n",
    "        gaze_speed_x = features['Gaze_X'].diff().abs()\n",
    "        gaze_speed_y = features['Gaze_Y'].diff().abs()\n",
    "        stats['gaze_speed_x_mean'] = gaze_speed_x.mean()\n",
    "        stats['gaze_speed_y_mean'] = gaze_speed_y.mean()\n",
    "        \n",
    "        # 表情变化率\n",
    "        expr_change = features['Expression'].diff().abs()\n",
    "        stats['expr_change_mean'] = expr_change.mean()\n",
    "        stats['expr_change_std'] = expr_change.std()\n",
    "        \n",
    "        return pd.Series(stats)\n",
    "    \n",
    "    except Exception as e:\n",
    "        print(f\"处理文件 {file_path} 出错: {e}\")\n",
    "        return None\n",
    "\n",
    "# 读取文件夹内所有CSV文件\n",
    "def read_subject_data(folder_path, label):\n",
    "    \"\"\"\n",
    "    读取文件夹内所有受试者数据，提取特征并添加标签\n",
    "    :param folder_path: 文件夹路径\n",
    "    :param label: 标签（1=ASD，0=TD）\n",
    "    :return: 特征DataFrame和标签Series\n",
    "    \"\"\"\n",
    "    print(f\"正在读取 {folder_path} ...\")\n",
    "    if not os.path.exists(folder_path):\n",
    "        print(f\"错误：文件夹 {folder_path} 不存在！\")\n",
    "        return pd.DataFrame(), pd.Series()\n",
    "    \n",
    "    all_features = []\n",
    "    file_names = []\n",
    "    \n",
    "    # 遍历文件夹中的所有CSV文件\n",
    "    for file in os.listdir(folder_path):\n",
    "        if file.endswith('.csv'):\n",
    "            file_path = os.path.join(folder_path, file)\n",
    "            file_names.append(file)\n",
    "            \n",
    "            # 处理单个文件\n",
    "            features = process_subject_file(file_path)\n",
    "            if features is not None:\n",
    "                all_features.append(features)\n",
    "    \n",
    "    if not all_features:\n",
    "        print(f\"警告：文件夹 {folder_path} 中没有有效数据\")\n",
    "        return pd.DataFrame(), pd.Series()\n",
    "    \n",
    "    # 转换为DataFrame\n",
    "    df_features = pd.concat(all_features, axis=1).T\n",
    "    df_features['label'] = label\n",
    "    df_features['file_name'] = file_names\n",
    "    \n",
    "    print(f\"成功读取 {len(df_features)} 个样本\")\n",
    "    return df_features\n",
    "\n",
    "# 主函数\n",
    "def main():\n",
    "    # 请替换为实际解压后的文件夹路径\n",
    "    asd_folder = r'C:\\Users\\jiang\\Desktop\\python结课\\ASD'\n",
    "    td_folder = r'C:\\Users\\jiang\\Desktop\\python结课\\TD'\n",
    "    \n",
    "    # 读取数据\n",
    "    asd_data = read_subject_data(asd_folder, 1)\n",
    "    td_data = read_subject_data(td_folder, 0)\n",
    "    \n",
    "    # 检查是否有数据\n",
    "    if asd_data.empty or td_data.empty:\n",
    "        print(\"错误：没有足够的数据用于训练。请检查数据路径和格式。\")\n",
    "        return\n",
    "    \n",
    "    # 合并数据\n",
    "    all_data = pd.concat([asd_data, td_data], ignore_index=True)\n",
    "    print(f\"\\n数据集合并完成，共有 {len(all_data)} 个样本\")\n",
    "    \n",
    "    # 分离特征和标签\n",
    "    X = all_data.drop(['label', 'file_name'], axis=1)\n",
    "    y = all_data['label']\n",
    "    \n",
    "    # 特征选择（保留最重要的特征）\n",
    "    selector = SelectKBest(score_func=f_classif, k=min(50, X.shape[1]))  # 最多保留50个特征\n",
    "    X_selected = selector.fit_transform(X, y)\n",
    "    \n",
    "    # 获取选择的特征名称\n",
    "    selected_features = X.columns[selector.get_support()]\n",
    "    print(f\"\\n特征选择完成，保留了 {len(selected_features)} 个特征:\")\n",
    "    print(selected_features.tolist())\n",
    "    \n",
    "    # 划分训练集和测试集\n",
    "    X_train, X_test, y_train, y_test = train_test_split(\n",
    "        X_selected, y, test_size=0.2, stratify=y, random_state=42\n",
    "    )\n",
    "    \n",
    "    # 打印数据集统计信息\n",
    "    print(f\"\\n数据集统计：\")\n",
    "    print(f\"总样本数: {len(X)}\")\n",
    "    print(f\"训练集样本数: {len(X_train)} ({len(X_train)/len(X):.0%})\")\n",
    "    print(f\"测试集样本数: {len(X_test)} ({len(X_test)/len(X):.0%})\")\n",
    "    print(f\"训练集标签分布: {y_train.value_counts()}\")\n",
    "    print(f\"测试集标签分布: {y_test.value_counts()}\")\n",
    "    \n",
    "    # 定义多个模型进行比较\n",
    "    models = {\n",
    "        \"SVM\": SVC(probability=True, random_state=42),\n",
    "        \"随机森林\": RandomForestClassifier(n_estimators=100, n_jobs=-1, random_state=42),\n",
    "        \"梯度提升树\": GradientBoostingClassifier(random_state=42)\n",
    "    }\n",
    "    \n",
    "    # 存储结果用于后续比较\n",
    "    results = {}\n",
    "    \n",
    "    # 训练和评估每个模型\n",
    "    for model_name, model in models.items():\n",
    "        print(f\"\\n=== {model_name} 模型训练与评估 ===\")\n",
    "        \n",
    "        # 创建模型管道（包含标准化）\n",
    "        pipeline = make_pipeline(StandardScaler(), model)\n",
    "        \n",
    "        # 训练模型\n",
    "        start_time = time.time()\n",
    "        pipeline.fit(X_train, y_train)\n",
    "        train_time = time.time() - start_time\n",
    "        print(f\"训练时间: {train_time:.2f} 秒\")\n",
    "        \n",
    "        # 预测\n",
    "        y_pred = pipeline.predict(X_test)\n",
    "        y_prob = pipeline.predict_proba(X_test)[:, 1]  # 正类概率\n",
    "        \n",
    "        # 评估\n",
    "        accuracy = accuracy_score(y_test, y_pred)\n",
    "        print(f\"准确率: {accuracy:.4f}\")\n",
    "        print(f\"分类报告:\\n{classification_report(y_test, y_pred, target_names=['TD', 'ASD'])}\")\n",
    "        \n",
    "        # 计算混淆矩阵\n",
    "        cm = confusion_matrix(y_test, y_pred)\n",
    "        \n",
    "        # 计算ROC曲线和AUC\n",
    "        fpr, tpr, _ = roc_curve(y_test, y_prob)\n",
    "        roc_auc = auc(fpr, tpr)\n",
    "        \n",
    "        # 存储结果\n",
    "        results[model_name] = {\n",
    "            'accuracy': accuracy,\n",
    "            'cm': cm,\n",
    "            'fpr': fpr,\n",
    "            'tpr': tpr,\n",
    "            'roc_auc': roc_auc,\n",
    "            'model': pipeline\n",
    "        }\n",
    "        \n",
    "        # 绘制混淆矩阵热力图\n",
    "        plt.figure(figsize=(8, 6))\n",
    "        sns.heatmap(cm, annot=True, fmt='d', cmap='Blues', \n",
    "                    xticklabels=['TD(0)', 'ASD(1)'], \n",
    "                    yticklabels=['TD(0)', 'ASD(1)'])\n",
    "        plt.xlabel('预测标签')\n",
    "        plt.ylabel('真实标签')\n",
    "        plt.title(f'{model_name} 混淆矩阵')\n",
    "        plt.tight_layout()\n",
    "        plt.savefig(f'{model_name}_confusion_matrix.png')\n",
    "        plt.close()\n",
    "    \n",
    "    # 绘制ROC曲线比较\n",
    "    plt.figure(figsize=(10, 8))\n",
    "    for model_name, result in results.items():\n",
    "        plt.plot(result['fpr'], result['tpr'], \n",
    "                 label=f'{model_name} (AUC = {result[\"roc_auc\"]:.2f})')\n",
    "    \n",
    "    plt.plot([0, 1], [0, 1], 'k--')  # 随机猜测线\n",
    "    plt.xlim([0.0, 1.0])\n",
    "    plt.ylim([0.0, 1.05])\n",
    "    plt.xlabel('假阳性率 (1-特异性)')\n",
    "    plt.ylabel('真阳性率 (敏感性)')\n",
    "    plt.title('模型ROC曲线比较')\n",
    "    plt.legend(loc=\"lower right\")\n",
    "    plt.savefig('roc_comparison.png')\n",
    "    plt.close()\n",
    "    \n",
    "    # 特征重要性分析（仅随机森林）\n",
    "    if '随机森林' in results:\n",
    "        rf_model = results['随机森林']['model'].named_steps.get('randomforestclassifier')\n",
    "        if rf_model and hasattr(rf_model, 'feature_importances_'):\n",
    "            plt.figure(figsize=(12, 8))\n",
    "            importances = rf_model.feature_importances_\n",
    "            indices = np.argsort(importances)[::-1]\n",
    "            \n",
    "            # 取前20个重要特征\n",
    "            top_features = min(20, len(selected_features))\n",
    "            plt.bar(range(top_features), importances[indices[:top_features]])\n",
    "            plt.xticks(range(top_features), [selected_features[i] for i in indices[:top_features]], rotation=90)\n",
    "            plt.xlabel('特征')\n",
    "            plt.ylabel('重要性')\n",
    "            plt.title('随机森林 - 特征重要性')\n",
    "            plt.tight_layout()\n",
    "            plt.savefig('feature_importance.png')\n",
    "            plt.close()\n",
    "            \n",
    "            print(\"\\n特征重要性（随机森林 - 前20）:\")\n",
    "            for i in indices[:20]:\n",
    "                print(f\"{selected_features[i]}: {importances[i]:.4f}\")\n",
    "    \n",
    "    print(\"\\n=== 模型比较 ===\")\n",
    "    for model_name, result in results.items():\n",
    "        print(f\"{model_name}: 准确率 = {result['accuracy']:.4f}, AUC = {result['roc_auc']:.4f}\")\n",
    "\n",
    "if __name__ == \"__main__\":\n",
    "    main()"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
